package lv.solcraft.replication.server.dao;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import lv.solcraft.replication.server.bdb.Database;

import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;

public abstract class BaseDataAccess<PK, ENTITY> implements DataAccess<PK, ENTITY> {

    protected final PrimaryIndex<PK, ENTITY> primary;
    protected final Database database;
    protected final EntityStore store;

    public BaseDataAccess(EntityStore store, Database database, Class<PK> pkClass, Class<ENTITY> entityClass) {
        this.store = store;
        this.database = database;
        try {
            this.primary = store.getPrimaryIndex(pkClass, entityClass);
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not get primary index for store, " + entityClass.getName(), e);
        }
    }

    public boolean persistNew(ENTITY entity) {
        try {
            return primary.putNoOverwrite(database.activeTx(), entity);
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not persist (no-overwrite) entity: " + entity, e);
        }
    }

    public void persist(ENTITY entity) {
        try {
            primary.putNoReturn(database.activeTx(), entity);
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not persist entity: " + entity, e);
        }
    }

    public ENTITY find(PK id) {
        try {
            return afterRead(primary.get(database.activeTx(), id, LockMode.READ_COMMITTED));
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not find entity by id " + id, e);
        }
    }

    public void delete(PK id) {
        try {
            primary.delete(database.activeTx(), id);
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not delete entity by id: " + id, e);
        }
    }

    public long countAll() {
        try {
            return primary.count();
        } catch (DatabaseException e) {
            throw new DataAccessException("Could not count all by primar index", e);
        }
    }

    public List<ENTITY> findFirst(int limit) {
        return findBatch(0, limit);
    }

    public List<ENTITY> findBatch(final int skip, final int limit) {
        try {
            List<ENTITY> result = new ArrayList<ENTITY>();
            EntityCursor<ENTITY> keys = primary.entities(database.activeTx(), new CursorConfig());
            try {
                int count = 0;
                for (Iterator<ENTITY> it = keys.iterator(); it.hasNext();) {
                    ENTITY entity = it.next();
                    if (count++ < skip)
                        continue;
                    result.add(afterRead(entity));
                    if (result.size() >= limit)
                        return result;
                }
                return result;
            } finally {
                keys.close();
            }
        } catch (DatabaseException e) {
            throw new DataAccessException("Error while finding batch from primary index", e);
        }
    }

    public <SK> List<ENTITY> findBatch(SecondaryIndex<SK, PK, ENTITY> index, final int skip, final int limit) {
        try {
            List<ENTITY> result = new ArrayList<ENTITY>();
            EntityCursor<ENTITY> keys = index.entities(database.activeTx(), new CursorConfig());
            try {
                int count = 0;
                for (Iterator<ENTITY> it = keys.iterator(); it.hasNext();) {
                    ENTITY entity = it.next();
                    if (count++ < skip)
                        continue;
                    result.add(afterRead(entity));
                    if (result.size() >= limit)
                        return result;
                }
                return result;
            } finally {
                keys.close();
            }
        } catch (DatabaseException e) {
            throw new DataAccessException("Error while finding batch from secondary index", e);
        }
    }

    protected abstract ENTITY afterRead(ENTITY entity);
}
